#include <string.h>
#include <stdio.h>

#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>

#include "wifi.h"
#include "uart.h"
#include "event.h"

uint8_t client_num = 0;

static const char *TAG = "===> wifi";
static int8_t wifi_disconnect = -1;

struct sock_struct {
    int enable;
    int sockt_id;
    char addr_str[16];
};

SemaphoreHandle_t binary_sem;

struct sock_struct sockt_array[MAX_CLIENT_NUM];

/* clinet -> sep32 -> machine */
#define SEND_BUF_SIZE   1024

int updata_maxfd(fd_set fds, int maxfd)
{
	int i, new_maxfd = 0;
	for (i = 0; i < maxfd; i++) {
		if (FD_ISSET(i, &fds) && i > new_maxfd)
			new_maxfd = i;
	}

	return new_maxfd;
}

int tcp_send(char* data, int len)
{
	int i = 0;
	if (client_num == 0)
		return -1;

	for (i = 0; i < MAX_CLIENT_NUM; i++) {
		if (sockt_array[i].enable) {
			send(sockt_array[i].sockt_id, data, len, 0);
		}
	}
	return 0;
}

static void tcp_server_task(void *pvParameters)
{
    char addr_str[16];
    char warning1[] = "The number of client connections has reached the maximum, and the connection failed!\n";
    char warning2[] = "The client has already connected, please do not connect again!\n";
	char data[SEND_BUF_SIZE];
    int addr_family = (int)pvParameters;
    int ip_protocol = 0;
    int keepAlive = 1;
    int keepIdle = KEEPALIVE_IDLE;
    int keepInterval = KEEPALIVE_INTERVAL;
    int keepCount = KEEPALIVE_COUNT;
    struct sockaddr_storage dest_addr;
    int i, j;

    if (addr_family == AF_INET) {
        struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;
        dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
        dest_addr_ip4->sin_family = AF_INET;
        dest_addr_ip4->sin_port = htons(PORT);
        ip_protocol = IPPROTO_IP;
    }

    int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);
    if (listen_sock < 0) {
        ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
        vTaskDelete(NULL);
        return;
    }
    int opt = 1;
    setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    ESP_LOGI(TAG, "Socket created");

    int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
    if (err != 0) {
        ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
        ESP_LOGE(TAG, "IPPROTO: %d", addr_family);
        goto CLEAN_UP;
    }
    ESP_LOGI(TAG, "Socket bound, port %d", PORT);

    err = listen(listen_sock, 1);
    if (err != 0) {
        ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
        goto CLEAN_UP;
    }

	fd_set readfds;
	fd_set readfds_back;
	struct timeval timeout;
	int maxfd, ret;
	int len;
	maxfd = listen_sock;
	FD_ZERO(&readfds);
	FD_ZERO(&readfds_back);

	FD_SET(listen_sock, &readfds_back);
	struct event_struct wifi_event;

    while (1) {
		readfds = readfds_back;
		updata_maxfd(readfds, maxfd);
		timeout.tv_sec = 1;
		timeout.tv_usec = 0;

		ret = select(maxfd + 1, &readfds, NULL, NULL, &timeout);
		if (ret == -1) {
			printf("select failed!\n");
		} else if (ret == 0) {
			/* select timeout! */
			/* 检查是否要关闭wifi断开所有客户端 */
			if (wifi_disconnect == 1) {
				printf("will close socket!\n");
				for (i = 0; i < MAX_CLIENT_NUM; i++) {
					if(sockt_array[i].enable) {
						//close(sockt_array[i].sockt_id);
						shutdown(sockt_array[i].sockt_id, 2);
						sockt_array[i].enable = 0;
						FD_CLR(sockt_array[i].sockt_id, &readfds_back);
					}
				}
				client_num = 0;
				memset(&wifi_event, 0, sizeof(wifi_event));
				wifi_event.event_type = (WIFI_EVENTS << 8)| WIFI_STA_DISCONNECTED;
				wifi_event.wifi.client_num = client_num;
				xQueueSend(evt_queue, &wifi_event, portMAX_DELAY);

				wifi_disconnect = -1;
				xSemaphoreGive(binary_sem);
			}
			continue;
		}

		for (j = listen_sock; j <= maxfd; j++) {
			if (!FD_ISSET(j, &readfds)) {
				continue;
			}
			/* 服务器套接字，有客户请求连接 */
			if (j == listen_sock) {
				struct sockaddr_storage source_addr;
				memset(&wifi_event, 0, sizeof(wifi_event));
				socklen_t addr_len = sizeof(source_addr);
				int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
				if (sock < 0) {
					ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
					break;
				}

				// Set tcp keepalive option
				setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int));
				setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int));
				setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int));
				setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));
				// Convert ip address to string
				if (source_addr.ss_family == PF_INET) {
					inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);
				}
				ESP_LOGI(TAG, "socket : %d, Socket accepted ip address: %s", sock, addr_str);

				for (i = 0; i < MAX_CLIENT_NUM; i++) {
					if (sockt_array[i].enable == 1) {
						if (!memcmp(addr_str, sockt_array[i].addr_str, strlen(addr_str))) {
							send(sock, warning2, sizeof(warning2), 0);
							printf("reconnect close socket %d\n", sock);
							close(sock);
							break;
						}
					}
					if (sockt_array[i].enable == 0) {
						sockt_array[i].enable = 1;
						sockt_array[i].sockt_id = sock;
						memcpy(sockt_array[i].addr_str, addr_str, strlen(addr_str));
						client_num++;
						wifi_event.event_type = (WIFI_EVENTS << 8)| WIFI_CLIENT_CONNECTED;
						wifi_event.wifi.client_num = client_num;
						xQueueSend(evt_queue, &wifi_event, portMAX_DELAY);
						if (sock > maxfd) {
							maxfd = sock;
						}
						FD_SET(sock, &readfds_back);
						break;
					}
				}
				if (i == MAX_CLIENT_NUM) {
					ESP_LOGI(TAG, "Max client connect!\n");
					send(sock, warning1, sizeof(warning1), 0);
					shutdown(sock, 2);
				}
			} else {
				int sock = j;
				memset(&wifi_event, 0, sizeof(wifi_event));
				memset(data, 0, SEND_BUF_SIZE);
				len = recv(sock, data, SEND_BUF_SIZE - 1, 0);
				if (len <= 0) {
					if (len < 0) {
						ESP_LOGE(TAG, "Sokcet %d Error occurred during receiving: errno %d", sock, errno);
					}
					if (len == 0)
						ESP_LOGW(TAG, "Socket %d Connection closed\n", sock);

					for (i = 0; i < MAX_CLIENT_NUM; i++) {
						if (wifi_disconnect == 0) {
							if (sockt_array[i].sockt_id == sock) {
								FD_CLR(sock, &readfds_back);
								sockt_array[i].enable = 0;
								shutdown(sock, 2);
								client_num--;
								wifi_event.event_type = (WIFI_EVENTS << 8)| WIFI_CLIENT_DISCONNECTED;
								wifi_event.wifi.client_num = client_num;
								xQueueSend(evt_queue, &wifi_event, portMAX_DELAY);
								break;
							}
						}
					}
					break;
				} else {
					uart_write(data, len);
				}
			}
		}
    }

CLEAN_UP:
    close(listen_sock);
    vTaskDelete(NULL);
}

static void wifi_event_handler(void *arg, esp_event_base_t event_base,
                            int32_t event_id, void *event_data)
{
	struct event_struct wifi_event;
	memset(&wifi_event, 0, sizeof(wifi_event));
	if (event_base == WIFI_EVENT) {
		switch (event_id) {
			case WIFI_EVENT_STA_START:
				ESP_LOGI(TAG, "wifi sta start\n");
				wifi_event.event_type = (WIFI_EVENTS << 8) | WIFI_STA_CONNECTING;
				xQueueSend(evt_queue, &wifi_event, portMAX_DELAY);
				esp_wifi_connect();
			break;
			case WIFI_EVENT_STA_CONNECTED:
				wifi_event.event_type = (WIFI_EVENTS << 8)| WIFI_STA_GET_IP;
				xQueueSend(evt_queue, &wifi_event, portMAX_DELAY);
				ESP_LOGI(TAG, "wifi sta connected\n");
			break;
			case WIFI_EVENT_STA_DISCONNECTED:
				ESP_LOGI(TAG, "wifi sta disconnected\n");
				/* idf文档 docs/zh_CN/api-guides/wifi.rst
				 * 里面有描述
				 * 对于DISCONNECT事件
				 * 1. 调用了函数 :cpp:func:`esp_wifi_disconnect()` 或 :cpp:func:`esp_wifi_stop()`，且 Wi-Fi station 已成功连接至 AP。
				 * 2. 调用了函数 :cpp:func:`esp_wifi_connect()`，但 Wi-Fi 驱动程序因为某些原因未能成功连接至 AP，
			     *    例如：未扫描到目标 AP、验证超时等。或存在多个 SSID 相同的 AP，station 无法连接所有已找到的 AP，也将产生该事件。
				 * 3. Wi-Fi 连接因为某些原因而中断，例如：station 连续多次丢失 N beacon、AP 踢掉 station、AP 认证模式改变等。
				 * 对于以上3种情况
				 * 1属于人为的关闭wifi，所以不需要重新连接wifi
				 * 23需要重新连接wifi
				 * 所以这里会有一个变量wifi_disconnect用于判断是否需要重新连接wifi
				 */
				if (wifi_disconnect == 0) {
					wifi_event.event_type = (WIFI_EVENTS << 8) | WIFI_STA_CONNECTING;
					xQueueSend(evt_queue, &wifi_event, portMAX_DELAY);
					esp_wifi_connect();
				}
			break;
		}
	}
	if (event_base == IP_EVENT) {
		switch (event_id) {
			case IP_EVENT_STA_GOT_IP:
			{
				ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
				ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s\" address: " IPSTR, esp_netif_get_desc(event->esp_netif), IP2STR(&event->ip_info.ip));
				wifi_event.event_type = (WIFI_EVENTS << 8) | WIFI_STA_CONNECTED;
				sprintf(wifi_event.wifi.ip, IPSTR, IP2STR(&event->ip_info.ip));
				xQueueSend(evt_queue, &wifi_event, portMAX_DELAY);
				xTaskCreate(tcp_server_task, "tcp_server", 4096, (void*)AF_INET, 11, NULL);
			}
			break;
		}
	}
}

void wifi_init(void)
{
    ESP_ERROR_CHECK(esp_netif_init());
	wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
	ESP_ERROR_CHECK(esp_wifi_init(&cfg));
	esp_netif_create_default_wifi_sta();
	esp_wifi_set_default_wifi_sta_handlers();
	ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = CONFIG_EXAMPLE_WIFI_SSID,
            .password = CONFIG_EXAMPLE_WIFI_PASSWORD,
            .scan_method = EXAMPLE_WIFI_SCAN_METHOD,
            .sort_method = EXAMPLE_WIFI_CONNECT_AP_SORT_METHOD,
            .threshold.rssi = CONFIG_EXAMPLE_WIFI_SCAN_RSSI_THRESHOLD,
            .threshold.authmode = EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD,
        },
    };
    ESP_LOGI(TAG, "Connecting to %s...", wifi_config.sta.ssid);
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));

    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_event_handler, NULL));

	binary_sem = xSemaphoreCreateBinary();
}

void wifi_start(uint8_t enable)
{
	if (enable) {
		if (wifi_disconnect != 0) {
			wifi_disconnect = 0;
			esp_wifi_start();
		}
	} else {
		/* wifi没有启动的话不需要去关闭 */
		if (wifi_disconnect == -1)
			return;
		wifi_disconnect = 1;
		/* 等待所有的客户端关闭完成 */
		xSemaphoreTake(binary_sem, portMAX_DELAY);
		esp_wifi_stop();
	}
}
